using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
using Engine.OldSerialization;

namespace RuthlessConquest {
    public abstract class Message {
        public static uint MagicNumber;

        public static Dictionary<int, Type> MessageTypesByMessageId;

        public static Dictionary<Type, int> MessageIdsByMessageTypes;

        public static bool UseXmlSerialization;

        public static bool LogNetworkMessages;

        public Version ReceivedVersion;

        static Message() {
            MagicNumber = MakeFourCC("RtCq");
            MessageTypesByMessageId = new Dictionary<int, Type>();
            MessageIdsByMessageTypes = new Dictionary<Type, int>();
            UseXmlSerialization = false;
            LogNetworkMessages = false;
            TypeInfo[] array = (from t in typeof(Message).Assembly.DefinedTypes
                where typeof(Message).IsAssignableFrom(t) && !t.IsAbstract
                orderby t.Name
                select t).ToArray();
            for (int i = 0; i < array.Length; i++) {
                MessageTypesByMessageId[i] = array[i];
                MessageIdsByMessageTypes[array[i]] = i;
                Archive.SetTypeSerializationOptions(array[i], false, true);
            }
        }

        public static Message Read(byte[] bytes) {
            InputArchive inputArchive = bytes[0] != 60
                ? new BinaryInputArchive(new MemoryStream(bytes)) { Use7BitInts = true }
                : new XmlInputArchive(XElement.Parse(Encoding.UTF8.GetString(bytes)));
            uint value = 0u;
            inputArchive.Serialize("MagicNumber", ref value);
            if (value != MagicNumber) {
                throw new ProtocolViolationException($"Invalid network message magic number {value:X}.");
            }
            uint value2 = 0u;
            inputArchive.Serialize("Version", ref value2);
            Version receivedVersion = new((int)value2);
            if (receivedVersion.Major != VersionsManager.Version.Major
                || receivedVersion.Minor != VersionsManager.Version.Minor) {
                throw new ProtocolViolationException(
                    $"Invalid network message version (version received {receivedVersion.Major}.{receivedVersion.Minor}, version expected {VersionsManager.Version.Major}.{VersionsManager.Version.Minor})."
                );
            }
            byte b = inputArchive.Serialize<byte>("MessageId");
            if (MessageTypesByMessageId.TryGetValue(b, out Type value3)) {
                Message message = (Message)inputArchive.Serialize(value3.Name, value3);
                message.ReceivedVersion = receivedVersion;
                if (LogNetworkMessages) {
                    _ = inputArchive is XmlInputArchive;
                }
                return message;
            }
            throw new ProtocolViolationException($"Unknown message id {b}.");
        }

        public static byte[] Write(Message message) {
            OutputArchive outputArchive = !UseXmlSerialization
                ? new BinaryOutputArchive(new MemoryStream()) { Use7BitInts = true }
                : new XmlOutputArchive(new XElement("Message"));
            outputArchive.Serialize("MagicNumber", MagicNumber);
            outputArchive.Serialize("Version", (uint)VersionsManager.Version.Value);
            outputArchive.Serialize("MessageId", (byte)MessageIdsByMessageTypes[message.GetType()]);
            outputArchive.Serialize(message.GetType().Name, message.GetType(), message);
            byte[] result = !UseXmlSerialization
                ? ((MemoryStream)((BinaryOutputArchive)outputArchive).Stream).ToArray()
                : Encoding.UTF8.GetBytes(((XmlOutputArchive)outputArchive).Node.ToString());
            if (LogNetworkMessages) {
                _ = outputArchive is XmlOutputArchive;
            }
            return result;
        }

        public static uint MakeFourCC(string s) => s[0] + ((uint)s[1] << 8) + ((uint)s[2] << 16) + ((uint)s[3] << 24);
    }
}